Thread 사이의 동작은 순서가 정해져 있지 않다.
비정상 동작이 발생할 수 있다. -> 동기화 이슈
스레드 관리를 필요로 한다.
import threading
g_count=0
def thread_main():
global g_count
for i in range(10000):
g_count=g_count+1
threads=[]
for i in range(50):
th=threading.Thread(target=thread_main)
threads.append(th)
for th in threads:
th.start()
for th in threads: //50개의 threads 들이 연산이 끝날 때까지 기다림(동기화)
th.join()
print('g_count= ',g_count)
위의 간단한 코드에서 반복문 횟수를 100,000 이상으로 증가시키면, 계산값이 비정상적으로 출력된다.
위의 프로세스의 스레드를 각 3가지 연산으로 가정할 경우
아래와 같은 경우 문제가 발생한다.
반복문 횟수간 100000회 이상일 경우, Thread가 온전히 100000번 반복하지 않고, 다른 Thread로 Context Switching 발생
어느 부분을 실행하다가 Context switching이 발생할지 예측할 수가 없다.
context switching이 0.05초 간격으로 발생한다고 가정할 때(running->ready)
1만까지 더하는데 0.05초가 안 걸림—>중간에 context switching이 발생하지 않음
10만까지 더할 때 0.05초보다 더 오래 걸리기 때문에 Context switching이 발생
mutual exclusion(상호배제)
동기화 이슈 해결 방안
쓰레드는 프로세스 모든 데이터를 접근할 수 있으므로,
여러 스레드가 변경하는 공유 변수에 대해 Exclusive Access 필요
어느 한 스레드가 공유 변수를 갱신하는 동안 다른 스레드가 동시 접근하지 못하도록 막아야 한다.
임계 자원(critical resource)인계 영역(critical section)
import threading
g_count=0
def thread_main():
global g_count
lock.acquire()
for i in range(1000000):
g_count=g_count+1
lock.release()
lock=threading .Lock()
threads=[]
for i in range(50):
th=threading.Thread(target=thread_main)
threads.append(th)
for th in threads:
th.start()
for th in threads:
th.join()
print('g_count: ',g_count)
lock되어 있는 동안 thread는 하나만 실행 -> 동기화 문제 해결
파이썬은 멀티 쓰레드를 사용할 경우 하나의 프로세스를 여러개의 쓰레드로 나누어 실행을 하게 되고,
그로 인해서 위와 같이 100,000 이상의 숫자를 계산 시킬 경우 스케쥴러로 인해 작업이 엉키게 된다.
하지만, 파이썬은 각 쓰레드는 한번에 하나의 CPU에서만 처리된다. (멀티 코어를 활용할 수 없다.)
자세한 사항은 DataScience-DeepLearning-TensorFlow-TensorFlow 참고
대신 멀티 프로세싱은 사용가능하다.
from multiprocessing import Process, Queue
def work(id, start, end, result):
total = 0
for i in range(start, end):
total += i
result.put(total)
return
if __name__ == "__main__":
START, END = 0, 100000000
result = Queue()
th1 = Process(target=work, args=(1, START, END//2, result))
th2 = Process(target=work, args=(2, END//2, END, result))
th1.start()
th2.start()
th1.join()
th2.join()
result.put('STOP')
total = 0
while True:
tmp = result.get()
if tmp == 'STOP':
break
else:
total += tmp
print(f"Result: {total}")
멀티 프로세싱은 자원을 공유하지 않기 때문에(쓰레드의 경우 TEXT, DATA, BSS, HEAP 영역을 공유)
GIL이 필요 없다.